"""
DjangoBlog 用户认证视图模块
处理用户注册、登录、注销、密码找回等认证相关功能
基于Django的类视图和函数视图实现
"""

import logging
from django.utils.translation import gettext_lazy as _
from django.conf import settings
from django.contrib import auth
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth import get_user_model
from django.contrib.auth import logout
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.hashers import make_password
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.http.request import HttpRequest
from django.http.response import HttpResponse
from django.shortcuts import get_object_or_404
from django.shortcuts import render
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.http import url_has_allowed_host_and_scheme
from django.views import View
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import FormView, RedirectView

from djangoblog.utils import send_email, get_sha256, get_current_site, generate_code, delete_sidebar_cache
from . import utils
from .forms import RegisterForm, LoginForm, ForgetPasswordForm, ForgetPasswordCodeForm
from .models import BlogUser

# 配置日志记录器，用于记录用户认证相关的操作日志
logger = logging.getLogger(__name__)


class RegisterView(FormView):
    """
    用户注册视图
    处理新用户的注册流程，包括表单验证、邮箱验证等
    """
    
    form_class = RegisterForm  # 使用的注册表单类
    template_name = 'account/registration_form.html'  # 注册页面模板

    @method_decorator(csrf_protect)  # 添加CSRF保护装饰器，防止跨站请求伪造
    def dispatch(self, *args, **kwargs):
        """
        重写分发方法，添加CSRF保护
        所有请求都会先经过此方法
        """
        return super(RegisterView, self).dispatch(*args, **kwargs)

    def form_valid(self, form):
        """
        表单验证通过后的处理逻辑
        创建用户、发送验证邮件等
        """
        if form.is_valid():  # 再次验证表单，确保数据有效
            # 创建用户但不立即保存到数据库（save(False)表示不提交）
            user = form.save(False)
            user.is_active = False  # 设置用户为非激活状态，需要邮箱验证
            user.source = 'Register'  # 记录用户来源为注册
            user.save(True)  # 保存用户到数据库
            
            # 准备邮箱验证所需的数据
            site = get_current_site().domain  # 获取当前网站域名
            # 生成验证签名，使用双重SHA256加密提高安全性
            sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))

            # 开发环境下使用本地地址
            if settings.DEBUG:
                site = '127.0.0.1:8000'
            
            # 构建验证URL
            path = reverse('account:result')  # 生成结果页面的URL
            url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format(
                site=site, path=path, id=user.id, sign=sign)

            # 构建邮件内容
            content = """
                            <p>请点击下面链接验证您的邮箱</p>

                            <a href="{url}" rel="bookmark">{url}</a>

                            再次感谢您！
                            <br />
                            如果上面链接无法打开，请将此链接复制至浏览器。
                            {url}
                            """.format(url=url)
            
            # 发送验证邮件
            send_email(
                emailto=[
                    user.email,  # 收件人邮箱
                ],
                title='验证您的电子邮箱',  # 邮件标题
                content=content)  # 邮件内容

            # 重定向到结果页面，显示注册成功信息
            url = reverse('accounts:result') + \
                  '?type=register&id=' + str(user.id)
            return HttpResponseRedirect(url)
        else:
            # 表单验证失败，重新渲染表单并显示错误
            return self.render_to_response({
                'form': form
            })


class LogoutView(RedirectView):
    """
    用户注销视图
    处理用户退出登录，清理会话和缓存
    """
    
    url = '/login/'  # 注销后重定向的URL

    @method_decorator(never_cache)  # 禁止缓存此视图，确保每次都是最新状态
    def dispatch(self, request, *args, **kwargs):
        """
        重写分发方法，添加禁止缓存装饰器
        """
        return super(LogoutView, self).dispatch(request, *args, **kwargs)

    def get(self, request, *args, **kwargs):
        """
        处理GET请求，执行注销操作
        """
        logout(request)  # Django内置的注销函数，清理用户会话
        delete_sidebar_cache()  # 清理侧边栏缓存，确保数据更新
        return super(LogoutView, self).get(request, *args, **kwargs)  # 调用父类的get方法进行重定向


class LoginView(FormView):
    """
    用户登录视图
    处理用户登录认证，支持记住登录状态
    """
    
    form_class = LoginForm  # 使用的登录表单类
    template_name = 'account/login.html'  # 登录页面模板
    success_url = '/'  # 登录成功后的默认重定向URL
    redirect_field_name = REDIRECT_FIELD_NAME  # 重定向字段名，默认为'next'
    login_ttl = 2626560  # 记住登录状态的有效期，一个月（以秒为单位）

    @method_decorator(sensitive_post_parameters('password'))  # 敏感参数保护，防止密码泄露到日志
    @method_decorator(csrf_protect)  # CSRF保护
    @method_decorator(never_cache)  # 禁止缓存
    def dispatch(self, request, *args, **kwargs):
        """
        重写分发方法，添加多个安全装饰器
        """
        return super(LoginView, self).dispatch(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        """
        添加上下文数据，包括重定向URL
        """
        # 获取重定向URL，如果没有则使用默认首页
        redirect_to = self.request.GET.get(self.redirect_field_name)
        if redirect_to is None:
            redirect_to = '/'
        kwargs['redirect_to'] = redirect_to  # 将重定向URL添加到上下文

        return super(LoginView, self).get_context_data(**kwargs)

    def form_valid(self, form):
        """
        表单验证通过后的处理逻辑
        执行用户登录操作
        """
        # 使用Django内置的AuthenticationForm进行认证
        form = AuthenticationForm(data=self.request.POST, request=self.request)

        if form.is_valid():  # 如果认证成功
            delete_sidebar_cache()  # 清理侧边栏缓存
            logger.info(self.redirect_field_name)  # 记录重定向字段信息

            # 执行用户登录，设置用户会话
            auth.login(self.request, form.get_user())
            
            # 如果用户选择了"记住我"，设置会话过期时间
            if self.request.POST.get("remember"):
                self.request.session.set_expiry(self.login_ttl)  # 设置一个月有效期
            
            return super(LoginView, self).form_valid(form)  # 调用父类的form_valid方法
        else:
            # 认证失败，重新显示表单和错误信息
            return self.render_to_response({
                'form': form
            })

    def get_success_url(self):
        """
        获取登录成功后的重定向URL
        进行安全验证，防止开放重定向攻击
        """
        # 从POST数据中获取重定向URL
        redirect_to = self.request.POST.get(self.redirect_field_name)
        
        # 验证URL是否安全，防止重定向到外部恶意网站
        if not url_has_allowed_host_and_scheme(
                url=redirect_to, allowed_hosts=[
                    self.request.get_host()]):  # 只允许重定向到当前主机
            redirect_to = self.success_url  # 如果不安全，使用默认URL
        
        return redirect_to


def account_result(request):
    """
    账户操作结果页面视图函数
    显示注册结果、邮箱验证结果等信息
    """
    type = request.GET.get('type')  # 操作类型：register或validation
    id = request.GET.get('id')  # 用户ID

    # 根据ID获取用户对象，如果不存在返回404
    user = get_object_or_404(get_user_model(), id=id)
    logger.info(type)  # 记录操作类型
    
    # 如果用户已经激活，直接重定向到首页
    if user.is_active:
        return HttpResponseRedirect('/')
    
    # 处理注册和验证两种类型
    if type and type in ['register', 'validation']:
        if type == 'register':
            # 注册成功页面内容
            content = '''
    恭喜您注册成功，一封验证邮件已经发送到您的邮箱，请验证您的邮箱后登录本站。
    '''
            title = '注册成功'
        else:
            # 邮箱验证处理
            c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))  # 计算正确的签名
            sign = request.GET.get('sign')  # 获取URL中的签名
            
            # 验证签名是否匹配，防止恶意验证
            if sign != c_sign:
                return HttpResponseForbidden()  # 签名不匹配，拒绝访问
            
            # 激活用户账户
            user.is_active = True
            user.save()
            
            # 验证成功页面内容
            content = '''
            恭喜您已经成功的完成邮箱验证，您现在可以使用您的账号来登录本站。
            '''
            title = '验证成功'
        
        # 渲染结果页面
        return render(request, 'account/result.html', {
            'title': title,  # 页面标题
            'content': content  # 页面内容
        })
    else:
        # 类型参数错误，重定向到首页
        return HttpResponseRedirect('/')


class ForgetPasswordView(FormView):
    """
    忘记密码视图
    通过邮箱验证重置用户密码
    """
    
    form_class = ForgetPasswordForm  # 忘记密码表单
    template_name = 'account/forget_password.html'  # 模板文件

    def form_valid(self, form):
        """
        表单验证通过后的处理逻辑
        重置用户密码
        """
        if form.is_valid():  # 表单数据有效
            # 根据邮箱查找用户
            blog_user = BlogUser.objects.filter(email=form.cleaned_data.get("email")).get()
            
            # 使用新密码的哈希值更新用户密码
            blog_user.password = make_password(form.cleaned_data["new_password2"])
            blog_user.save()  # 保存更改
            
            # 重定向到登录页面
            return HttpResponseRedirect('/login/')
        else:
            # 表单验证失败，重新显示表单
            return self.render_to_response({'form': form})


class ForgetPasswordEmailCode(View):
    """
    忘记密码邮箱验证码视图
    发送密码重置验证码到用户邮箱
    """

    def post(self, request: HttpRequest):
        """
        处理POST请求，发送验证码邮件
        """
        form = ForgetPasswordCodeForm(request.POST)  # 初始化表单
        
        # 表单验证
        if not form.is_valid():
            return HttpResponse("错误的邮箱")  # 邮箱格式错误
        
        to_email = form.cleaned_data["email"]  # 获取验证通过的邮箱

        # 生成验证码并发送邮件
        code = generate_code()  # 生成随机验证码
        utils.send_verify_email(to_email, code)  # 发送验证邮件
        utils.set_code(to_email, code)  # 保存验证码到缓存或数据库

        return HttpResponse("ok")  # 返回成功响应